package com.treasure.web.exception;

import com.treasure.common.exception.RateLimitException;
import com.treasure.common.exception.TreasureUncheckedException;
import com.treasure.common.reponse.Response;
import com.treasure.common.reponse.ResponseCodeEnum;
import com.treasure.common.model.TraceIdHolder;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.Optional;

/**
 * @author sunhongmin
 * @date 2023/9/1 17:12
 * @description
 */
@ControllerAdvice
@Slf4j
@Order(2)
public class GlobalExceptionHandler {
    public static final String TRACE_ID_KEY = "traceId";

    @ExceptionHandler({TreasureUncheckedException.class})
    @ResponseBody
    public Response<String> handleServiceException(Exception ex, HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        if (ex instanceof RateLimitException) {
            printErrorLog(ex, url, getParameters(request));
            return Response.error(ResponseCodeEnum.INTERNAL_ERROR, ex.getMessage());
        }

        printErrorLog(ex, url, getParameters(request));
        return Response.error(ResponseCodeEnum.SERVICE_ERROR, ex.getMessage());
    }


    @ExceptionHandler({Exception.class})
    @ResponseBody
    public Response<String> handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
        String traceId = TraceIdHolder.getTraceId();
        if (Objects.isNull(traceId)) {
            traceId = TraceIdHolder.genTraceId();
        }
        MDC.put(TRACE_ID_KEY, traceId);
        response.setHeader(TRACE_ID_KEY, traceId);
        String url = request.getRequestURL().toString();
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) ex;
            StringBuilder message = new StringBuilder();
            validException.getBindingResult().getAllErrors().forEach(err -> {
                message.append(err.getDefaultMessage()).append(".");
            });
            return Response.error(ResponseCodeEnum.INTERNAL_ERROR, message.toString());
        } else if (ex instanceof HttpRequestMethodNotSupportedException) {
            printErrorLog(ex, url, getParameters(request));
            return Response.error(ResponseCodeEnum.INTERNAL_ERROR, ex.getMessage());
        } else if (ex instanceof MaxUploadSizeExceededException) {
            printErrorLog(ex, url, getParameters(request));
            return Response.error(ResponseCodeEnum.INTERNAL_ERROR, ex.getMessage());
        } else {
            printErrorLog(ex, url, getParameters(request));
            return Response.error(ResponseCodeEnum.INTERNAL_ERROR, "系统异常（traceId: " + traceId + ")");
        }
    }

    private static void printErrorLog(Exception ex, String url, Object requestParameters) {
        StringBuilder exceptionMsg = new StringBuilder();
        exceptionMsg.append("\n\t");
        exceptionMsg.append("访问链接：");
        exceptionMsg.append(url);
        exceptionMsg.append("\n\t");
        exceptionMsg.append("访问参数：");
        exceptionMsg.append(Optional.ofNullable(requestParameters).map(Objects::toString).orElse(""));
        exceptionMsg.append("\n\t");
        exceptionMsg.append("错误信息：");
        exceptionMsg.append(ex.getMessage());
        log.error(exceptionMsg.toString(), ex);
    }

    /**
     * 根据请求类型 获取请求参数
     *
     * @param request
     * @return
     */
    private Object getParameters(HttpServletRequest request) {
        if (null != request.getContentType() && request.getContentType().contains("application/json")) {
            return RequestParamUtil.getBodyString(request);
        } else {
            return request.getParameterMap();
        }
    }
}
